using System;
using System.Web;
using System.Text;
using System.Web.Security;
using System.Reflection;

namespace Univar.Helpers
{
	/// <summary>
	/// A class to encrypt and decrypt cookies in a tamper-proof manner.
	/// It uses reflection to access the System.Web.Security.CookieProtectionHelper internal
	/// class which is what ASP.NET uses for MachineKey based cookie authorization.
	/// </summary>
	public static class CookieEncryptor
	{
		private static MethodInfo _methodEncode;
		private static MethodInfo _methodDecode;
		private const string INTERNAL_CLASS_NAME = "System.Web.Security.CookieProtectionHelper";

		static CookieEncryptor()
		{
			// Get a reference to System.Web assembly
			Assembly systemWeb = typeof(HttpContext).Assembly;
			if (systemWeb == null)
				throw new InvalidOperationException("Unable to load System.Web.");

			// Get a reference to the internal class CookieProtectionHelper
			Type cookieProtectionHelper = systemWeb.GetType(INTERNAL_CLASS_NAME);

			if (cookieProtectionHelper == null)
				throw new InvalidOperationException("Unable to get the internal class " + INTERNAL_CLASS_NAME);

			// Get references to the methods of CookieProtectionHelper class
			_methodEncode = cookieProtectionHelper.GetMethod("Encode", BindingFlags.NonPublic | BindingFlags.Static);
			_methodDecode = cookieProtectionHelper.GetMethod("Decode", BindingFlags.NonPublic | BindingFlags.Static);

			if (_methodEncode == null || _methodDecode == null)
				throw new InvalidOperationException("Unable to get references to the encode/decode methods.");
		}

		public static string Encrypt(string text)
		{
			return Encrypt(text, CookieProtection.All); 
		}

		public static string Encrypt(string text, CookieProtection cookieProtection)
		{
			byte[] buf = Encoding.UTF8.GetBytes(text);
			return (string)_methodEncode.Invoke(null, new object[] { cookieProtection, buf, buf.Length });
		}

		/// <summary>
		/// Decrypts a cookie that has all levels of cookie protection.
		/// </summary>
		public static string Decrypt(string text, bool throwExceptionOnError)
		{
			return Decrypt(text, CookieProtection.All, throwExceptionOnError);
		}

		/// <summary>
		/// Decodes a string.
		/// </summary>
		/// <param name="text">String to decode.</param>
		/// <param name="cookieProtection">The method in which the string is protected.</param>
		/// <param name="throwExceptionOnError">Throw an exception message when an error occurs
		/// instead of returning a null value.</param>
		/// <returns>The decrypted string or throws InvalidCastException if tampered with.</returns>
		public static string Decrypt(string text, CookieProtection cookieProtection, bool throwExceptionOnError)
		{
			if (string.IsNullOrEmpty(text))
				return text;

			byte[] buf;
			try
			{
				buf = (byte[])_methodDecode.Invoke(null, new object[] { cookieProtection, text });
				if (buf == null || buf.Length == 0)
					throw new Exception();
			}
			catch (Exception ex)
			{
				if (throwExceptionOnError)
					throw new InvalidCastException("Unable to decrypt the text", ex.InnerException);
				else
					return null;
			}

			return Encoding.UTF8.GetString(buf, 0, buf.Length);
		}
	}
}